/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmium;

import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CadmiumException;
import fr.x9c.cadmium.kernel.Channel;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.graph.GraphSlot;
import fr.x9c.cadmium.util.Misc;

/**
 * This class provides base Cadmium primitives as well as some utility methods
 * for other Cadmium primitive providers.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
@PrimitiveProvider
public final class Cadmium {

    /** Value for 'abstract' modifier. */
    private static final Value MODIFIER_ABSTRACT = Value.ZERO;

    /** Value for 'final' modifier. */
    private static final Value MODIFIER_FINAL = Value.ONE;

    /** Value for 'interface' modifier. */
    private static final Value MODIFIER_INTERFACE = Value.TWO;

    /** Value for 'native' modifier. */
    private static final Value MODIFIER_NATIVE = Value.createFromLong(3);

    /** Value for 'private' modifier. */
    private static final Value MODIFIER_PRIVATE = Value.createFromLong(4);

    /** Value for 'protected' modifier. */
    private static final Value MODIFIER_PROTECTED = Value.createFromLong(5);

    /** Value for 'public' modifier. */
    private static final Value MODIFIER_PUBLIC = Value.createFromLong(6);

    /** Value for 'static' modifier. */
    private static final Value MODIFIER_STATIC = Value.createFromLong(7);

    /** Value for 'strictfp' modifier. */
    private static final Value MODIFIER_STRICTFP = Value.createFromLong(8);

    /** Value for 'synchronized' modifier. */
    private static final Value MODIFIER_SYNCHRONIZED = Value.createFromLong(9);

    /** Value for 'transcient' modifier. */
    private static final Value MODIFIER_TRANSCIENT = Value.createFromLong(10);

    /** Value for 'volatile' modifier. */
    private static final Value MODIFIER_VOLATILE = Value.createFromLong(11);

    /** Tag for 'boolean' type. */
    private static final int TYPE_BOOLEAN = 0;

    /** Tag for 'byte' type. */
    private static final int TYPE_BYTE = 1;

    /** Tag for 'char' type. */
    private static final int TYPE_CHAR = 2;

    /** Tag for 'double' type. */
    private static final int TYPE_DOUBLE = 3;

    /** Tag for 'float' type. */
    private static final int TYPE_FLOAT = 4;

    /** Tag for 'int' type. */
    private static final int TYPE_INT = 5;

    /** Tag for 'long' type. */
    private static final int TYPE_LONG = 6;

    /** Tag for 'short' type. */
    private static final int TYPE_SHORT = 7;

    /** Tag for 'string' type. */
    private static final int TYPE_STRING = 8;

    /** Tag for 'class' type. */
    private static final int TYPE_CLASS_TAG = 0;

    /** Tag for 'void' type. */
    private static final int TYPE_VOID = 9;

    /** Tag for 'array' type. */
    private static final int TYPE_ARRAY_TAG = 1;

    /** Value for 'boolean' type. */
    private static final Value TYPE_BOOLEAN_VALUE = Value.ZERO;

    /** Value for 'byte' type. */
    private static final Value TYPE_BYTE_VALUE = Value.ONE;

    /** Value for 'char' type. */
    private static final Value TYPE_CHAR_VALUE = Value.TWO;

    /** Value for 'double' type. */
    private static final Value TYPE_DOUBLE_VALUE = Value.createFromLong(3);

    /** Value for 'float' type. */
    private static final Value TYPE_FLOAT_VALUE = Value.createFromLong(4);

    /** Value for 'int' type. */
    private static final Value TYPE_INT_VALUE = Value.createFromLong(5);

    /** Value for 'long' type. */
    private static final Value TYPE_LONG_VALUE = Value.createFromLong(6);

    /** Value for 'short' type. */
    private static final Value TYPE_SHORT_VALUE = Value.createFromLong(7);

    /** Value for 'string' type. */
    private static final Value TYPE_STRING_VALUE = Value.createFromLong(8);

    /** Value for 'void' type. */
    private static final Value TYPE_VOID_VALUE = Value.createFromLong(9);

    /** Tag for 'boolean' value. */
    private static final int TAG_BOOLEAN = 0;

    /** Tag for 'byte' value. */
    private static final int TAG_BYTE = 1;

    /** Tag for 'char' value. */
    private static final int TAG_CHAR = 2;

    /** Tag for 'double' value. */
    private static final int TAG_DOUBLE = 3;

    /** Tag for 'float' value. */
    private static final int TAG_FLOAT = 4;

    /** Tag for 'int' value. */
    private static final int TAG_INT = 5;

    /** Tag for 'long' value. */
    private static final int TAG_LONG = 6;

    /** Tag for 'short' value. */
    private static final int TAG_SHORT = 7;

    /** Tag for 'string' value. */
    private static final int TAG_STRING = 8;

    /** Tag for 'object' value. */
    private static final int TAG_OBJECT = 9;

    /**
     * No instance of this class.
     */
    private Cadmium() {
    } // end empty constructor

    /**
     * Tests whether Cadmium is available.
     * @param ctxt context
     * @param unit ignored
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE}
     */
    @Primitive
    public static Value cadmium_is_available(final CodeRunner ctxt,
                                             final Value unit) {
        return Value.TRUE;
    } // end method 'cadmium_is_available(CodeRunner, Value)'

    /**
     * Tests whether a custom object is <tt>null</tt>.
     * @param ctxt context
     * @param obj obj to test
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if the object is <tt>null</tt>,
     *         {@link fr.x9c.cadmium.kernel.Value#FALSE} otherwise
     */
    @Primitive
    public static Value cadmium_is_null(final CodeRunner ctxt,
                                        final Value obj) {
        return obj.asBlock().asCustom() == null
            ? Value.TRUE
            : Value.FALSE;
    } // end method 'cadmium_is_null(CodeRunner, Value)'

    /**
     * Constructs a custom object that is <tt>null</tt>.
     * @param ctxt context
     * @param unit ignored
     * @return a custom object that is <tt>null</tt>
     */
    @Primitive
    public static Value cadmium_get_null(final CodeRunner ctxt,
                                         final Value unit) {
        return createObject(null);
    } // end method 'cadmium_get_null(CodeRunner, Value)'

    /**
     * Tests whether an instance is of a given class
     * (equivalent to <tt>instanceof</tt> operator).
     * @param ctxt context
     * @param obj object to test
     * @param clss class to test against
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if the object is
     *         an instance is the passed class,
     *         {@link fr.x9c.cadmium.kernel.Value#FALSE} otherwise
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_instance_of(final CodeRunner ctxt,
                                            final Value obj,
                                            final Value clss)
        throws Fail.Exception {
        try {
            final Object o = obj.asBlock().asCustom();
            final Class c = (Class) clss.asBlock().asCustom();
            return c.isInstance(o) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_instance_of(CodeRunner, Value, Value)'

    /**
     * Converts a string value into a Java string.
     * @param ctxt context
     * @param s string value to convert
     * @return a Java object corresponding to passed string
     */
    @Primitive
    public static Value cadmium_object_of_string(final CodeRunner ctxt,
                                                 final Value s) {
        return createObject(s.asBlock().asString());
    } // end method 'cadmium_object_of_string(CodeRunner, Value, Value)'

    /**
     * Converts a <i>char</i> into a Java character.
     * @param ctxt context
     * @param c <i>char</i> to convert
     * @return result of conversion
     */
    @Primitive
    public static Value cadmium_java_char_of_char(final CodeRunner ctxt,
                                                  final Value c) {
        return Value.createFromLong((int) Misc.convertByteToChar((byte) Misc.unsignedToSignedByte(c.asLong() & 0xFF)));
    } // end method 'cadmium_java_char_of_char(CodeRunner, Value)'

    /**
     * Converts a Java character into a <i>char</i>.
     * @param ctxt context
     * @param c Java character to convert
     * @return result of conversion
     */
    @Primitive
    public static Value cadmium_char_of_java_char(final CodeRunner ctxt,
                                                  final Value c) {
        return Value.createFromLong(Misc.signedToUnsignedByte(Misc.convertCharToByte((char) c.asLong())));
    } // end method 'cadmium_char_of_java_char(CodeRunner, Value)'

    /**
     * Tests whether two objects are equal
     * (equivalent to <tt>==</tt> operator).
     * @param obj first object to test
     * @param obj2 second object to test
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if the objects are
     *         equal according to <tt>==</tt>,
     *         {@link fr.x9c.cadmium.kernel.Value#FALSE} otherwise
     */
    @Primitive
    public static Value cadmium_equal(final CodeRunner ctxt,
                                      final Value obj,
                                      final Value obj2) {
        return obj.asBlock().asCustom() == obj2.asBlock().asCustom()
            ? Value.TRUE
            : Value.FALSE;
    } // end method 'cadmium_equal(CodeRunner, Value, Value)'

    /**
     * Tests whether two objects are different
     * (equivalent to <tt>!=</tt> operator).
     * @param obj first object to test
     * @param obj2 second object to test
     * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if the objects are
     *         different according to <tt>!=</tt>,
     *         {@link fr.x9c.cadmium.kernel.Value#FALSE} otherwise
     */
    @Primitive
    public static Value cadmium_not_equal(final CodeRunner ctxt,
                                          final Value obj,
                                          final Value obj2) {
        return obj.asBlock().asCustom() != obj2.asBlock().asCustom()
            ? Value.TRUE
            : Value.FALSE;
    } // end method 'cadmium_not_equal(CodeRunner, Value, Value)'

    /**
     * Prints the stack trace of a Java exception onto an output channel.
     * @param ctxt context
     * @param out ouput channel
     * @param exn exception to print
     * @return <i>unit</i>
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_print_stack_trace(final CodeRunner ctxt,
                                                  final Value out,
                                                  final Value exn)
        throws Fail.Exception {
        try {
            final Channel ch = (Channel) out.asBlock().asCustom();
            final PrintStream ps = new PrintStream(ch.asOutputStream(), true);
            ((Throwable) exn.asBlock().asCustom()).printStackTrace(ps);
            return Value.UNIT;
        } catch (final Exception e) {
            fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_print_stack_trace(CodeRunner, Value, Value)'

    /**
     * Throws a Java exception.
     * @param ctxt context
     * @param exn exception to throw
     * @return <i>unit</i>
     * @throws Fail.Exception if <tt>exn</tt> is not a Java exception
     */
    @Primitive
    public static Value cadmium_throw_exception(final CodeRunner ctxt,
                                                final Value exn)
        throws Fail.Exception {
        final Object obj = exn.asBlock().asCustom();
        if (obj instanceof Exception) {
            fail(ctxt, (Exception) obj);
        } else {
            Fail.invalidArgument("throw_exception");
        } // end if/else
        return Value.UNIT; // never reached
    } // end method 'cadmium_throw_exception(CodeRunner, Value)'

    /**
     * Executes a function under a synchronized block.
     * @param ctxt context
     * @param obj object to acquire lock over
     * @param f function to execute under lock
     * @return <i>unit</i>
     */
    @Primitive
    public static Value cadmium_synchronized(final CodeRunner ctxt,
                                             final Value obj,
                                             final Value f)
        throws Fail.Exception, Fatal.Exception {
        try {
            synchronized (obj.asBlock().asCustom()) {
                try {
                    ctxt.callback(f, Value.UNIT);
                } catch (final CadmiumException ie) {
                    Fatal.raise("error in synchronized block: " + ie.toString());
                } // end try/catch
            } // end synchronized
        } catch (final Exception e) {
            fail(ctxt, e);
        } // end try/catch
        return Value.UNIT;
    } // end method 'cadmium_synchronized(CodeRunner, Value, Value)'

    /**
     * Returns the frame used by the 'Graphics' module.
     * @param ctxt context
     * @param unit ignored
     * @return the frame used by the 'Graphics' module, can be <tt>null</tt> if
     *         no frame is used
     * @throws Fail.Exception if the 'Graphics' module has not been initialized
     */
    @Primitive
    public static Value cadmium_get_frame(final CodeRunner ctxt,
                                          final Value unit)
        throws Fail.Exception {
        final GraphSlot slot =
            (GraphSlot) ctxt.getContext().getSlot(GraphSlot.SLOT);
        if (slot != null) {
            return createObject(slot.getFrame());
        } else {
            Fail.invalidArgument("Cadmium.get_frame");
            return Value.UNIT; // never reached
        } // end if/else
    } // end method 'cadmium_get_frame(CodeRunner, Value)'

    /**
     * Returns the canvas used by the 'Graphics' module.
     * @param ctxt context
     * @param unit ignored
     * @return the canvas used by the 'Graphics' module
     * @throws Fail.Exception if the 'Graphics' module has not been initialized
     */
    @Primitive
    public static Value cadmium_get_canvas(final CodeRunner ctxt,
                                           final Value unit)
        throws Fail.Exception {
        final GraphSlot slot =
            (GraphSlot) ctxt.getContext().getSlot(GraphSlot.SLOT);
        if (slot != null) {
            return createObject(slot.getCanvas());
        } else {
            Fail.invalidArgument("Cadmium.get_canvas");
            return Value.UNIT; // never reached
        } // end if/else
    } // end method 'cadmium_get_canvas(CodeRunner, Value)'

    /**
     * Returns the buffer used by the 'Graphics' module.
     * @param ctxt context
     * @param unit ignored
     * @return the buffer used by the 'Graphics' module
     * @throws Fail.Exception if the 'Graphics' module has not been initialized
     */
    @Primitive
    public static Value cadmium_get_buffer(final CodeRunner ctxt,
                                           final Value unit)
        throws Fail.Exception {
        final GraphSlot slot =
            (GraphSlot) ctxt.getContext().getSlot(GraphSlot.SLOT);
        if (slot != null) {
            return createObject(slot.getBuffer());
        } else {
            Fail.invalidArgument("Cadmium.get_buffer");
            return Value.UNIT; // never reached
        } // end if/else
    } // end method 'cadmium_get_buffer(CodeRunner, Value)'

    /**
     * Returns the binding for passed identifier.
     * @param ctxt context
     * @param id binding identifier
     * @return the value associated with the passed identifier
     * @throws Fail.Exception no binding exist with the passed identifier
     */
    @Primitive
    public static Value cadmium_get_binding(final CodeRunner ctxt,
                                            final Value id)
        throws Fail.Exception {
        final String key = id.asBlock().asString();
        final Map<String, Value> bindings = ctxt.getBindings();
        if ((bindings != null) && bindings.containsKey(key)) {
            return bindings.get(key);
        } else {
            Fail.raiseNotFound();
            return null; // never reacher
        } // end if/else
    } // end method 'cadmium_get_binding(CodeRunner, Value)'

    /**
     * Tests whether an identifier binds a Java object value.
     * @param ctxt context
     * @param id binding identifier
     * @return <tt>true</tt> if the passed identifier binds a Java Object
     *         value, <tt>false</tt> otherwise
     * @throws Fail.Exception no binding exist with the passed identifier
     */
    @Primitive
    public static Value cadmium_is_binding_object(final CodeRunner ctxt,
                                                  final Value id)
        throws Fail.Exception {
        final String key = id.asBlock().asString();
        final Map<String, Value> bindings = ctxt.getBindings();
        if (bindings.containsKey(key)) {
            final Value v = bindings.get(key);
            if (v.isBlock()
                && (v.asBlock().getCustomOperations() != null)
                && (v.asBlock().getCustomOperations().getIdentifier().equals(CustomObject.OPS.getIdentifier()))) {
                return Value.TRUE;
            } else {
                return Value.FALSE;
            } // end if/else
        } else {
            Fail.raiseNotFound();
            return null; // never reacher
        } // end if/else
    } // end method 'cadmium_is_binding_object(CodeRunner, Value)'

    /**
     * Raises a <i>Java_exception</i> corresponding to an exception.
     * @param ctxt context
     * @param e exception
     * @throws Fail.Exception always
     */
    public static void fail(final CodeRunner ctxt,
                            final Exception e)
        throws Fail.Exception {
        final Value exn =
            ctxt.getContext().getCallback("Cadmium.Java_exception");
        if (exn == null) {
            Fail.invalidArgument("Exception Cadmium.Java_exception not initialized");
        } else {
            final String msg = e.getMessage();
            final Block b = Block.createBlock(0,
                                              Value.createFromBlock(Block.createString(e.getClass().getName())),
                                              Value.createFromBlock(Block.createString(msg != null ? msg : "")),
                                              createObject(e));
            Fail.raiseWithArg(exn, Value.createFromBlock(b));
        } // end if/else
    } // end method 'fail(CodeRunner, Exception)'

    /**
     * Constructs a custom type value from a Java instance. <br/>
     * The type of the value is <i>Cadmium.java_object</i>.
     * @param obj instance to wrap
     * @return a custom type value wrapping the passed instance
     */
    public static Value createObject(final Object obj) {
        final Block res = Block.createCustom(CustomObject.SIZE,
                                             CustomObject.OPS);
        res.setCustom(obj);
        return Value.createFromBlock(res);
    } // end method 'createObject(Object)'

    /**
     * Constructs an object list from an object array.
     * @param o object array
     * @return a list containing all the element of <tt>o</tt> in the same order
     */
    static Value createObjectList(final Object[] o) {
        Value res = Value.EMPTY_LIST;
        final int len = o.length;
        for (int i = len - 1; i >= 0; i--) {
            final Block b = Block.createBlock(Block.TAG_CONS,
                                              createObject(o[i]),
                                              res);
            res = Value.createFromBlock(b);
        } // end for
        return res;
    } // end method 'createObject(Object[])'

    /**
     * Constructs an object list from a value array.
     * @param elems value array
     * @return a list containing all the element of <tt>elems</tt> in the same order
     */
    static Value createList(final Value[] elems) {
        Value res = Value.EMPTY_LIST;
        final int len = elems.length;
        for (int i = len - 1; i >= 0; i--) {
            final Block b = Block.createBlock(Block.TAG_CONS,
                                              elems[i],
                                              res);
            res = Value.createFromBlock(b);
        } // end for
        return res;
    } // end method 'createList(Value[])'

    /**
     * Constructs a list from an integer representing element modifiers. <br/>
     * <i>Cf.</i> {@link java.lang.reflect.Modifier}
     * @param m element modifiers
     * @return a list containing all modifiers of <tt>m</tt>
     */
    static Value createModifiersList(final int m) {
        final List<Value> l = new ArrayList<Value>();
        if (Modifier.isAbstract(m)) {
            l.add(Cadmium.MODIFIER_ABSTRACT);
        } // end if
        if (Modifier.isFinal(m)) {
            l.add(Cadmium.MODIFIER_FINAL);
        } // end if
        if (Modifier.isInterface(m)) {
            l.add(Cadmium.MODIFIER_INTERFACE);
        } // end if
        if (Modifier.isNative(m)) {
            l.add(Cadmium.MODIFIER_NATIVE);
        } // end if
        if (Modifier.isPrivate(m)) {
            l.add(Cadmium.MODIFIER_PRIVATE);
        } // end if
        if (Modifier.isProtected(m)) {
            l.add(Cadmium.MODIFIER_PROTECTED);
        } // end if
        if (Modifier.isPublic(m)) {
            l.add(Cadmium.MODIFIER_PUBLIC);
        } // end if
        if (Modifier.isStatic(m)) {
            l.add(Cadmium.MODIFIER_STATIC);
        } // end if
        if (Modifier.isStrict(m)) {
            l.add(Cadmium.MODIFIER_STRICTFP);
        } // end if
        if (Modifier.isSynchronized(m)) {
            l.add(Cadmium.MODIFIER_SYNCHRONIZED);
        } // end if
        if (Modifier.isTransient(m)) {
            l.add(Cadmium.MODIFIER_TRANSCIENT);
        } // end if
        if (Modifier.isVolatile(m)) {
            l.add(Cadmium.MODIFIER_VOLATILE);
        } // end if

        Value res = Value.EMPTY_LIST;
        for (Value v : l) {
            final Block b = Block.createBlock(Block.TAG_CONS, v, res);
            res = Value.createFromBlock(b);
        } // end for
        return res;
    } // end method 'createModifiersList(int)'

    /**
     * Constructs an integer array from a list of integers.
     * @param ints list of integers
     * @return integer array containing all the elements of <tt>ints</tt> in
     *         the same order
     */
    static int[] decodeIntList(final Value ints) {
        final List<Integer> res = new ArrayList<Integer>();
        Value list = ints;
        while (list.isBlock()) {
            final Block listBlock = list.asBlock();
            res.add(listBlock.get(0).asLong());
            list = listBlock.get(1);
        } // end while
        final int len = res.size();
        final int[] tmp = new int[len];
        for (int i = 0; i < len; i++) {
            tmp[i] = res.get(i);
        } // end for
        return tmp;
    } // end method 'decodeIntList(Value)'

    /**
     * Constructs a Java type (as a {@link java.lang.Class}) from a ocaml
     * value (of <i>java_type</i> type).
     * @param type ocaml value of <i>java_type</i> type
     * @return corresponding Java representation of type
     */
    static Class decodeType(final Value type) {
        if (type.isLong()) {
            switch (type.asLong()) {
            case Cadmium.TYPE_BOOLEAN:
                return boolean.class;
            case Cadmium.TYPE_BYTE:
                return byte.class;
            case Cadmium.TYPE_CHAR:
                return char.class;
            case Cadmium.TYPE_DOUBLE:
                return double.class;
            case Cadmium.TYPE_FLOAT:
                return float.class;
            case Cadmium.TYPE_INT:
                return int.class;
            case Cadmium.TYPE_LONG:
                return long.class;
            case Cadmium.TYPE_SHORT:
                return short.class;
            case Cadmium.TYPE_STRING:
                return String.class;
            case Cadmium.TYPE_VOID:
                return Void.class;
            default:
                assert false : "invalid type value";
                return null;
            } // end switch
        } else {
            final Block typeBlock = type.asBlock();
            switch (typeBlock.getTag()) {
            case Cadmium.TYPE_CLASS_TAG:
                return (Class) typeBlock.get(0).asBlock().asCustom();
            case Cadmium.TYPE_ARRAY_TAG:
                return Array.newInstance(decodeType(typeBlock.get(0)), 0).getClass();
            default:
                assert false : "invalid type value";
                return null;
            } // end switch
        } // end if/else
    } // end method 'decodeType(Value)'

    /**
     * Constructs a ocaml value (of <i>java_type</i> type)
     * from a Java type (as a {@link java.lang.Class}).
     * @param type Java representation of type
     * @return corresponding ocaml value of <i>java_type</i> type
     */
    static Value encodeType(final Class type) {
        if (type.isArray()) {
            final Block res =
                Block.createBlock(Cadmium.TYPE_ARRAY_TAG,
                                  encodeType(type.getComponentType()));
            return Value.createFromBlock(res);
        } else if (type.equals(boolean.class)) {
            return Cadmium.TYPE_BOOLEAN_VALUE;
        } else if (type.equals(byte.class)) {
            return Cadmium.TYPE_BYTE_VALUE;
        } else if (type.equals(char.class)) {
            return Cadmium.TYPE_CHAR_VALUE;
        } else if (type.equals(double.class)) {
            return Cadmium.TYPE_DOUBLE_VALUE;
        } else if (type.equals(float.class)) {
            return Cadmium.TYPE_FLOAT_VALUE;
        } else if (type.equals(int.class)) {
            return Cadmium.TYPE_INT_VALUE;
        } else if (type.equals(long.class)) {
            return Cadmium.TYPE_LONG_VALUE;
        } else if (type.equals(short.class)) {
            return Cadmium.TYPE_SHORT_VALUE;
        } else if (type.equals(String.class)) {
            return Cadmium.TYPE_STRING_VALUE;
        } else if (type.equals(Void.class)) {
            return Cadmium.TYPE_VOID_VALUE;
        } else {
            final Block res = Block.createBlock(Cadmium.TYPE_CLASS_TAG,
                                                createObject(type));
            return Value.createFromBlock(res);
        } // end if/elsif/else
    } // end method 'encodeType(Class)'

    /**
     * Constructs a Java type array (as a <tt>java.lang.Class[]</tt>) from a
     * list of ocaml values (of <i>java_type</i> type).
     * @param types list of ocaml values of <i>java_type</i> type
     * @return corresponding Java representation of type list
     */
    static Class[] decodeTypeList(final Value types) {
        final List<Class> res = new ArrayList<Class>();
        Value list = types;
        while (list.isBlock()) {
            final Block listBlock = list.asBlock();
            res.add(decodeType(listBlock.get(0)));
            list = listBlock.get(1);
        } // end while
        final Class[] tmp = new Class[res.size()];
        return res.toArray(tmp);
    } // end method 'decodeTypeList(Value)'

    /**
     * Constructs a list of ocaml values (of <i>java_type</i> type)
     * from a Java type array (as a <tt>java.lang.Class[]</tt>).
     * @param types Java type list
     * @return corresponding list of ocaml values of <i>java_type</i> type
     */
    static Value encodeTypeList(final Class[] types) {
        Value res = Value.EMPTY_LIST;
        final int len = types.length;
        for (int i = len - 1; i >= 0; i--) {
            final Block b = Block.createBlock(Block.TAG_CONS,
                                              encodeType(types[i]),
                                              res);
            res = Value.createFromBlock(b);
        } // end for
        return res;
    } // end method 'encodeTypeList(Class[])'

    /**
     * Converts a Java instance into a ocaml value.
     * @param obj instance to convert
     * @return conversion of <tt>obj</tt> as a value
     */
    static Value encodeObject(final Object obj) {
        if (obj instanceof Boolean) {
            final Value v = ((Boolean) obj).booleanValue()
                ? Value.TRUE
                : Value.FALSE;
            final Block bl = Block.createBlock(Cadmium.TAG_BOOLEAN, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Byte) {
            final Value v = Value.createFromLong(((Byte) obj).byteValue());
            final Block bl = Block.createBlock(Cadmium.TAG_BYTE, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Character) {
            final Value v = Value.createFromLong(((Character) obj).charValue());
            final Block bl = Block.createBlock(Cadmium.TAG_CHAR, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Double) {
            final Value v = Value.createFromBlock(Block.createDouble(((Double) obj).doubleValue()));
            final Block bl = Block.createBlock(Cadmium.TAG_DOUBLE, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Float) {
            final Value v = Value.createFromBlock(Block.createDouble(((Float) obj).doubleValue()));
            final Block bl = Block.createBlock(Cadmium.TAG_FLOAT, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Integer) {
            final Block tmp = Block.createCustom(Custom.INT_32_SIZE,
                                                 Custom.INT_32_OPS);
            tmp.setInt32(((Integer) obj).intValue());
            final Value v = Value.createFromBlock(tmp);
            final Block bl = Block.createBlock(Cadmium.TAG_INT, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Long) {
            final Block tmp = Block.createCustom(Custom.INT_64_SIZE,
                                                 Custom.INT_64_OPS);
            tmp.setInt64(((Long) obj).longValue());
            final Value v = Value.createFromBlock(tmp);
            final Block bl = Block.createBlock(Cadmium.TAG_LONG, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof Short) {
            final Value v = Value.createFromLong(((Short) obj).shortValue());
            final Block bl = Block.createBlock(Cadmium.TAG_SHORT, v);
            return Value.createFromBlock(bl);
        } else if (obj instanceof String) {
            final Value v =
                Value.createFromBlock(Block.createString((String) obj));
            final Block bl = Block.createBlock(Cadmium.TAG_STRING, v);
            return Value.createFromBlock(bl);
        } else {
            final Block tmp = Block.createCustom(CustomObject.SIZE,
                                                 CustomObject.OPS);
            tmp.setCustom(obj);
            final Value v = Value.createFromBlock(tmp);
            final Block bl = Block.createBlock(Cadmium.TAG_OBJECT, v);
            return Value.createFromBlock(bl);
        } // end if/elsif/else
    } // end method 'encodeObject(Object)'

    /**
     * Convert a ocaml value into a Java instance.
     * @param val value to convert
     * @return conversion of <tt>val</tt> as a Java object
     */
    static Object decodeObject(final Value val) {
        if (val == null) {
            return null;
        } else if (val.isBlock()) {
            final Block bl = val.asBlock();
            switch (bl.getTag()) {
            case Cadmium.TAG_BOOLEAN: // argument: bool
                return bl.get(0) == Value.TRUE ? Boolean.TRUE : Boolean.FALSE;
            case Cadmium.TAG_BYTE: // argument: int
                return new Byte((byte) bl.get(0).asLong());
            case Cadmium.TAG_CHAR: // argument: int
                return new Character((char) bl.get(0).asLong());
            case Cadmium.TAG_DOUBLE: // argument: float
                return new Double(bl.get(0).asBlock().asDouble());
            case Cadmium.TAG_FLOAT: // argument: float
                return new Float(bl.get(0).asBlock().asDouble());
            case Cadmium.TAG_INT: // argument: int32
                return new Integer(bl.get(0).asBlock().asInt32());
            case Cadmium.TAG_LONG: // argument: int64
                return new Long(bl.get(0).asBlock().asInt64());
            case Cadmium.TAG_SHORT: // argument: int
                return new Short((short) bl.get(0).asLong());
            case Cadmium.TAG_STRING: // argument: string
                return bl.get(0).asBlock().asString();
            case Cadmium.TAG_OBJECT: // argument: object
                return bl.get(0).asBlock().asCustom();
            default:
                assert false : "invalid value";
                return null;
            } // end switch
        } else {
            assert false : "invalid value";
            return null;
        } // end if/elsif/else
    } // end method 'decodeObject(Value)'

    /**
     * Convert a list of ocaml values into an array of Java instances.
     * @param values list of values to convert
     * @return conversion of <tt>values</tt> as an array of Java objects
     */
    static Object[] decodeObjectList(final Value values) {
        final List<Object> res = new ArrayList<Object>();
        Value list = values;
        while (list.isBlock()) {
            final Block listBlock = list.asBlock();
            res.add(decodeObject(listBlock.get(0)));
            list = listBlock.get(1);
        } // end while
        final Object[] tmp = new Object[res.size()];
        return res.toArray(tmp);
    } // end method 'decodeObjectList(Value)'

} // end class 'Cadmium'
